package zdream.pmfield.stage.director;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;

import zdream.pmfield.core.Result;
import zdream.pmfield.stage.action.BuffAction;
import zdream.pmfield.stage.action.DazeAction;
import zdream.pmfield.stage.action.MovementAction;
import zdream.pmfield.stage.buff.Buffs;
import zdream.pmfield.stage.message.ActionAbortMessage;
import zdream.pmfield.stage.message.ActionRequestReply;
import zdream.pmfield.stage.message.FoeArriveMessage;
import zdream.pmfield.stage.message.TimelineOrderChangeMessage;
import zdream.pmfield.stage.message.TurnFinishedMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.stage.Stage;
import zdream.pmfield.stage.Timeline;
import zdream.pmfield.stage.action.AttackAction;
import zdream.pmfield.stage.action.StageAction;
import zdream.pmfield.stage.director.AttackContext.AttackContextItem;
import zdream.pmfield.stage.effect.Effects;
import zdream.pmfield.stage.message.ActionStartMessage;
import zdream.pmfield.stage.message.ActionFinishedMessage;
import zdream.pmfield.stage.message.ActionRequestMessage;
import zdream.pmfield.stage.message.AttackMessage;
import zdream.pmfield.stage.message.FoeLeaveMessage;
import zdream.pmfield.stage.message.TurnStartMessage;
import zdream.pmfield.stage.message.RoundPassMessage;
import zdream.pmfield.stage.message.StageFinishedMessage;
import zdream.pmfield.stage.message.StageMessage;
import zdream.pmfield.stage.object.ObjectOnSeat;

/**
 * 生命周期
 *
 * <pre>
 * while (finished) {
 *
 *   if (next.isFoe) {
 *     send(TurnStartMessage)
 *
 *     while (foe.hasActionPoints) {
 *       send(ActionRequestMessage) (may not)
 *       send(ActionStartMessage)
 *       // handle action
 *       send(ActionFinishedMessage)
 *     }
 *
 *     send(TurnFinishedMessage)
 *   } else {
 *     send(ActionStartMessage)
 *     // handle action
 *     send(ActionFinishedMessage)
 *   }
 * }
 * </pre>
 *
 * @author Zdream
 * @date 2023-11-13
 * @since 0.0.1
 */
public class Director {
	public final Stage stage;

	private final List<IntSupplier>
			plans = new ArrayList<>(),
			works = new ArrayList<>();
	private final List<StageMessage> messageQueue = new ArrayList<>();

	public static class Turn {
		public final FoeActor owner;
		public boolean movable = true;

		public Turn(FoeActor owner) {
			this.owner = Objects.requireNonNull(owner);
		}
	}

	/**
	 * 如果在某一个 turn 中, this.turn 必不为 null;
	 * 如果不在任何一个 turn 中, this.turn 必为 null
	 */
	private Turn turn;

	private StageAction action;
	private AttackContext attackContext;
	public final Archives archives = new Archives();
	public final Effects effects = new Effects();
	public final Buffs buffs = new Buffs();
	private Consumer<StageResult> stageResultConsumer;

	private int lastRound = 0;

	public static final String ROLL_TITLE_MOVE_POINT = FoeActor.TITLE_MOVE_POINT;
	public static final String ROLL_TITLE_ACTION = AttackContext.TITLE_ACTION;

	public Director(Stage stage) {
		this.stage = stage;
	}

	public void init() {
		this.plans.add(this::checkRound);
		this.plans.add(this::turnStartIfNextIsFoe);
		this.plans.add(this::waitForAction);
		this.plans.add(this::evaluateAction);
		this.plans.add(this::executeAction);
		this.plans.add(this::actionFinished);
	}

	/**
	 * @return can be null
	 */
	public StageMessage popMessage() {
		if (messageQueue.isEmpty()) {
			return null;
		}
		return messageQueue.remove(0);
	}

	public boolean hasMessageRemain() {
		return !messageQueue.isEmpty();
	}

	public void pushMessage(StageMessage message) {
		messageQueue.add(Objects.requireNonNull(message));
	}

	public void run() {
		if (works.isEmpty()) {
			works.addAll(plans);
		}

		final IntSupplier work = works.remove(0);
		work.getAsInt();
	}

	private int checkRound() {
		final Timeline.TimelineItem next = stage.timeline.next();
		final int nextRound = (int) Math.ceil(next.time);

		if (lastRound == nextRound) {
			return 0;
		}

		while (lastRound != nextRound) {
			lastRound++;
			pushMessage(new RoundPassMessage(lastRound));
		}
		return 0;
	}

	private int turnStartIfNextIsFoe() {
		final Timeline.TimelineItem next = stage.timeline.untilNext();
		this.action = null;

		if (next.isForFoe()) {
			nextTurnForFoe(next.asFoe());
			tryToFetchNextAction();
		} else if (next.isForAction()) {
			this.action = next.asAction().action;
		} else {
			throw new IllegalArgumentException("还没做好: 我们只完成了下一个是 Foe / Action 行动的");
		}

		return 0;
	}

	private void nextTurnForFoe(Timeline.FoeTimelineItem next) {
		turn = new Turn(next.foe);

		if (!next.newTurn) {
			return;
		}

		pushMessage(new TurnStartMessage(turn.owner));

		// hooks 里面可能往 timeline 里面放 action. 所以后面的 action 都往 timeline 里面放和取
		Hooks.BeforeTurnHookContext turnHooks = turn.owner.triggerHook(HookType.BEFORE_FOE_TURN_START).as();
		turnHooks.withMovePointRollCount(2).withFoe(turn.owner).run();

		turn.movable = turnHooks.allowMove;
	}

	private void tryToFetchNextAction() {
		final boolean hasRemain = stage.timeline.hasRemainingActionNow();
		if (hasRemain) {
			action = stage.timeline.untilNext().asAction().action;
		}
	}

	private int waitForAction() {
		if (action != null) {
			// may be it is an action triggered by StageBuff
			return 0;
		}

		turn.owner.triggerHook(HookType.BEFORE_FOE_ACTION_REQUEST).run();

		final ActionRequestMessage message = new ActionRequestMessage(turn);
		message.validator = reply -> validateActionRequestReply(message, reply);
		pushMessage(message);
		return 0;
	}

	private Result validateActionRequestReply(ActionRequestMessage message, ActionRequestReply reply) {
		final ActionRequestReply replyCopy = reply.copy();
		Result result = stage.moveChecker.validateActionRequestReply(message, replyCopy);
		if (result.success) {
			this.action = replyCopy.action;
		}
		return result;
	}

	private int evaluateAction() {
		pushMessage(new ActionStartMessage(action));

		if (action.isForAttack()) {
			evaluateAttack(action.asAttack());
		} else if (action.isForDaze()) {
			evaluateDaze(action.asDaze());
		}
		// BuffAction will be skipped
		// MovementAction will be skipped
		// DazeAction will be skipped

		return 0;
	}

	private int executeAction() {
		if (action.isForAttack()) {
			executeAttack(action.asAttack());
		} else if (action.isForDaze()) {
			// do-nothing
		} else if (action.isForBuff()) {
			executeBuff(action.asBuff());
		} else if (action.isForMovement()) {
			executeMovement(action.asMovement());
		} else {
			throw new RuntimeException("这个 action 是" + action.getClass().getName() + ", 现在还没做怎么触发");
		}
		return 0;
	}

	private void evaluateAttack(AttackAction attack) {
		AttackContext context = AttackContext.create(attack);
		this.attackContext = context;

		stage.moveChecker.deductActionPoints(context);
		context.attacker.addMoveExecutingBuff(context.currentMove);

		stage.moveChecker.evaluateAttack(context);
	}

	private void evaluateDaze(DazeAction asDaze) {
		this.turn.owner.actionPoint = 0;
	}

	private void executeAttack(AttackAction attack) {
		final AttackContext context = attackContext;
		final FoeActor attacker = context.attacker;

		if (context.abort) {
			pushMessage(new ActionAbortMessage(context.action, context.abortReason));
		} else {
			// 6. 伤害
			stage.moveChecker.executeAttackDamage(context);
			pushMessage(new AttackMessage(context.report));

			// 7. 附加效果 (攻击方)
			// 防御方的效果是在 timeline 上面加个 action, 后续触发
			Hooks.AttackHookContext hooks = attacker.triggerHook(HookType.AFTER_EXECUTE_ATTACK).as();
			hooks.withAttackContext(context).run();

			// 8. 附加效果 (防御方)
			for (AttackContextItem item : context.items) {
				final FoeActor targetFoe = item.targetFoe;
				if (targetFoe == null) {
					continue;
				}
				hooks = targetFoe.triggerHook(HookType.AFTER_EXECUTE_DEFENSE).as();
				hooks.withAttackContext(context).run();
			}
		}

		// 9. 行动数据归档
		archives.save(context.report);

		// 10. 发动结束
		if (context.speedCostExtraRate != 0 && this.turn != null) {
			float nextTurnRemainFrom = attacker.speedCost * attacker.speedCostRate;
			attacker.speedCostRate += context.speedCostExtraRate;
			float nextTurnRemainTo = attacker.speedCost * attacker.speedCostRate;

			TimelineOrderChangeMessage message = new TimelineOrderChangeMessage(attacker)
					.withSpeedRemainChanged(nextTurnRemainFrom, nextTurnRemainTo);
			pushMessage(message);
		}

		attacker.removeMoveExecutingBuff();
	}

	private void executeBuff(BuffAction buffAction) {
		buffAction.buff.dispatchTask(buffAction);
	}

	private void executeMovement(MovementAction movementAction) {
		final ObjectOnSeat target = movementAction.target;
		if (target.seat != movementAction.from) {
			throw new IllegalArgumentException(target + " 的位置不在 " + movementAction.from + " 而在 " + target.seat);
		}

		if (target.isFoe()) {
			pushMessage(FoeLeaveMessage.fromMovement(target.asFoe()));
		} else {
			throw new RuntimeException("其它的 target, 如果发送 FoeLeaveMessage 信息呢?");
		}
		target.leave();

		target.arrive(movementAction.to);
		if (target.isFoe()) {
			pushMessage(new FoeArriveMessage(target.asFoe(), movementAction.to));
		} else {
			throw new RuntimeException("其它的 target, 如果发送 FoeArriveMessage 信息呢?");
		}
	}

	private int actionFinished() {
		pushMessage(new ActionFinishedMessage());

		if (turn != null && !stage.timeline.hasRemainingActionNow()) {
			// 当 Foe 有剩余的行动力时, 下回合还是它行动
			if (turn.owner.hasActionPoint() && !turn.owner.isFainting()) {
				stage.timeline.putFoeWithRemainingActionPoint(turn.owner);
			}
		}

		// 检查是否有退场的
		for (FoeActor foe : stage.allFoes) {
			if (foe.seat == null) {
				continue;
			}
			if (foe.isFainting()) {
				pushMessage(FoeLeaveMessage.fromFaint(foe));
				foe.leaveFromStage();
			}
		}

		// 先退场, 再处理是否轮次结束
		boolean shouldTurnFinished = !stage.timeline.hasRemainingActionNow();
		if (this.turn != null && shouldTurnFinished) {
			stage.timeline.flip();
			pushMessage(new TurnFinishedMessage(turn.owner));
			turn = null;
		}

		// 检查是否满足 finished 条件
		// 当前: 如果场地上 (Seat) 只剩下一方 foe, 则判定为 finished
		if (this.turn == null) {
			final Set<Integer> sides = stage.allFoes.stream()
					.filter(foe -> foe.seat != null)
					.map(foe -> foe.side)
					.collect(Collectors.toSet());
			if (sides.size() == 1) {
				final StageResult result = new StageResult(stage)
						.withSideSuccess(sides.iterator().next())
						.withOtherSideFail();
				this.stageResultConsumer.accept(result);
				pushMessage(new StageFinishedMessage(result));
			}
		}

		return 0;
	}

	public AttackContext getCurrentAttackContext() {
		return attackContext;
	}

	public void setFinishedHandler(Consumer<StageResult> handler) {
		this.stageResultConsumer = handler;
	}
}
